﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Ast.Framework.Caching;
using Ast.Framework.Environment.Extensions.Models;
using Ast.Framework.FileSystems.SiteFolders;
using Ast.Framework.Localization;
using Ast.Framework.Utility.Extensions;
using Castle.Core.Logging;

namespace Ast.Framework.Environment.Extensions.Folders
{
    public class ExtensionHarvester : IExtensionHarvester
    {
        private readonly ICacheManager _cacheManager;
        private readonly ISiteFolder _siteFolder;
        private readonly ICriticalErrorProvider _criticalErrorProvider;

        public ExtensionHarvester(ICacheManager cacheManager, ISiteFolder siteFolder, ICriticalErrorProvider criticalErrorProvider)
        {
            _cacheManager = cacheManager;
            _siteFolder = siteFolder;
            _criticalErrorProvider = criticalErrorProvider;

            Logger = NullLogger.Instance;
            T = NullLocalizer.Instance;
        }

        public Localizer T { get; set; }
        public ILogger Logger { get; set; }
        public bool DisableMonitoring { get; set; }

        public IEnumerable<ExtensionDescriptor> HarvestExtensions(IEnumerable<string> paths, string extensionType, string manifestName, bool manifestIsOptional)
        {
            return paths
                .SelectMany(path => HarvestExtensions(path, extensionType, manifestName, manifestIsOptional))
                .ToList();
        }

        private IEnumerable<ExtensionDescriptor> HarvestExtensions(string path, string extensionType, string manifestName, bool manifestIsOptional)
        {
            string key = string.Format("{0}-{1}-{2}", path, manifestName, extensionType);

            return _cacheManager.Get(key, ctx =>
            {
                if (!DisableMonitoring)
                {
                    Logger.DebugFormat("Monitoring path \"{0}\"", path);
                    ctx.Monitor(_siteFolder.WhenPathChanges(path));
                }

                return AvailableExtensionsInFolder(path, extensionType, manifestName, manifestIsOptional).ToReadOnlyCollection();
            });
        }

        private List<ExtensionDescriptor> AvailableExtensionsInFolder(string path, string extensionType, string manifestName, bool manifestIsOptional)
        {
            Logger.InfoFormat("Start looking for extensions in '{0}'...", path);
            var subfolderPaths = _siteFolder.ListDirectories(path);
            var localList = new List<ExtensionDescriptor>();
            foreach (var subfolderPath in subfolderPaths)
            {
                var extensionId = Path.GetFileName(subfolderPath.TrimEnd('/', '\\'));
                var manifestPath = Path.Combine(subfolderPath, manifestName);
                try
                {
                    var descriptor = GetExtensionDescriptor(path, extensionId, extensionType, manifestPath, manifestIsOptional);

                    if (descriptor == null)
                        continue;

                    if (descriptor.Path != null && !descriptor.Path.IsValidUrlSegment())
                    {
                        Logger.ErrorFormat("The module '{0}' could not be loaded because it has an invalid Path ({1}). It was ignored. The Path if specified must be a valid URL segment. The best bet is to stick with letters and numbers with no spaces.", extensionId, descriptor.Path);
                        _criticalErrorProvider.RegisterErrorMessage(T("The extension '{0}' could not be loaded because it has an invalid Path ({1}). It was ignored. The Path if specified must be a valid URL segment. The best bet is to stick with letters and numbers with no spaces.", extensionId, descriptor.Path));
                        continue;
                    }

                    if (descriptor.Path == null)
                    {
                        descriptor.Path = descriptor.Name.IsValidUrlSegment()
                            ? descriptor.Name
                            : descriptor.Id;
                    }

                    localList.Add(descriptor);
                }
                catch (Exception ex)
                {
                    // Ignore invalid module manifests
                    Logger.ErrorFormat(ex, "The module '{0}' could not be loaded. It was ignored.", extensionId);
                    _criticalErrorProvider.RegisterErrorMessage(T("The extension '{0}' manifest could not be loaded. It was ignored.", extensionId));
                }
            }
            Logger.InfoFormat("Done looking for extensions in '{0}': {1}", path, string.Join(", ", localList.Select(d => d.Id)));
            return localList;
        }

        private ExtensionDescriptor GetExtensionDescriptor(string locationPath, string extensionId, string extensionType, string manifestPath, bool manifestIsOptional)
        {
            return new ExtensionDescriptor
            {
                Location = locationPath,
                Id = extensionId,
                ExtensionType = extensionType,
                Name = extensionId
            };
        }
    }
}